package io;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

public class Tokenizer {
	private enum State {
		WHITE_SPACE,
		IN_SINGLE_QUOTE,
		IN_SINGLE_QUOTE_ESCAPED,
		IN_DOUBLE_QUOTE,
		IN_DOUBLE_QUOTE_ESCAPED,
		MAYBE_COMMENT,
		IN_ONE_LINE_COMMENT,
		IN_MULTI_LINE_COMMENT,
		MAYBE_END_MULTI_LINE_COMMENT,
		IN_ALPHA_NUMERIC,
		NOTHING
	}

	public static List<Token> tokenize(String filePath) throws Exception {		
		Reader reader = new BufferedReader(new FileReader(new File(filePath)));
		List<Token> result = new ArrayList<Token>();
		StringBuilder sb = new StringBuilder();
		State state = State.NOTHING;
		int i;
		while ((i = reader.read()) != -1) {
			char c = (char)i;
			boolean unprocessed = false;
			switch (state) {
			case WHITE_SPACE:
				if (c != ' ' && c != '\t') {
					result.add(new Token(TokenType.WHITE_SPACE, sb.toString()));
					state = State.NOTHING;
					unprocessed = true;
				} else {
					sb.append(c);
				}
				break;
			case IN_SINGLE_QUOTE:
				sb.append(c);
				if (c == '\\') {
					state = State.IN_SINGLE_QUOTE_ESCAPED;					
				} else if (c == '\'') {
					result.add(new Token(TokenType.QUOTE, sb.toString()));
					state = State.NOTHING;
				}
				break;
			case IN_SINGLE_QUOTE_ESCAPED:
				sb.append(c);
				state = State.IN_SINGLE_QUOTE;
				break;
			case IN_DOUBLE_QUOTE:
				sb.append(c);
				if (c == '\\') {
					state = State.IN_DOUBLE_QUOTE_ESCAPED;
				} else if (c == '"') {
					result.add(new Token(TokenType.WHITE_SPACE, sb.toString()));
					state = State.NOTHING;
				}
				break;
			case IN_DOUBLE_QUOTE_ESCAPED:
				sb.append(c);
				state = State.IN_DOUBLE_QUOTE;
				break;
			case MAYBE_COMMENT:
				if (c == '/') {
					sb.append(c);
					state = State.IN_ONE_LINE_COMMENT;
				} else if (c == '*') {
					sb.append(c);
					state = State.IN_MULTI_LINE_COMMENT;
				} else {
					result.add(new Token(TokenType.SYMBOL, sb.toString()));
					state = State.NOTHING;
					unprocessed = true;
				}
				break;
			case IN_ONE_LINE_COMMENT:
				sb.append(c);
				if (c == '\n') {
					result.add(new Token(TokenType.COMMENT, sb.toString()));
					state = State.NOTHING;
				}
				break;
			case IN_MULTI_LINE_COMMENT:
				sb.append(c);
				if (c == '*') {
					state = State.MAYBE_END_MULTI_LINE_COMMENT;
				}
				break;
			case MAYBE_END_MULTI_LINE_COMMENT:
				sb.append(c);
				if (c == '/') {
					result.add(new Token(TokenType.COMMENT, sb.toString()));
					state = State.NOTHING;
				}
				break;
			case IN_ALPHA_NUMERIC:
				if (!Character.isLetterOrDigit(c)) {
					result.add(new Token(TokenType.OTHER, sb.toString()));
					state = State.NOTHING;
					unprocessed = true;
				} else {
					sb.append(c);
					System.out.println(sb.toString());
				}
				break;
			default:
				unprocessed = true;
				break;
			}
			if (unprocessed) {
				sb = new StringBuilder();
				sb.append(c);
				if (c == '\n') {
					result.add(new Token(TokenType.END_OF_LINE, sb.toString()));
				} else if (c == ' ' || c == '\t' || c == '\r') {
					state = State.WHITE_SPACE;
				} else if (c == '/') {
					state = State.MAYBE_COMMENT;
				} else if (c == '\'') {
					state = State.IN_SINGLE_QUOTE;
				} else if (c == '"') {
					state = State.IN_DOUBLE_QUOTE;
				} else if (Character.isLetterOrDigit(c)) {
					state = State.IN_ALPHA_NUMERIC;
				} else {
					result.add(new Token(TokenType.SYMBOL, sb.toString()));
				}
			}
		}
		switch (state) {
		case WHITE_SPACE:
			result.add(new Token(TokenType.WHITE_SPACE, sb.toString()));
			break;
		case MAYBE_COMMENT:		
			result.add(new Token(TokenType.SYMBOL, sb.toString()));
			break;
		case IN_ONE_LINE_COMMENT:
			result.add(new Token(TokenType.COMMENT, sb.toString()));
			break;
		case IN_ALPHA_NUMERIC:
			result.add(new Token(TokenType.OTHER, sb.toString()));
			break;
		case NOTHING:
			break;
		default:
			result.add(new Token(TokenType.INVALID, ""));
		}		
		return result;
	}
}
